#include "nasal.h"
#include "nasal_type.h"
#include "nasal_gc.h"
#include "nasal_err.h"
#include "nasal_lexer.h"
#include "nasal_ast.h"
#include "nasal_parse.h"
#include "nasal_import.h"
#include "ast_visitor.h"
#include "ast_dumper.h"
#include "symbol_finder.h"
#include "optimizer.h"
#include "nasal_codegen.h"
#include "nasal_vm.h"
#include "nasal_dbg.h"
#include "repl.h"

#include <unordered_map>
#include <thread>
#include <cstdlib>

const u32 VM_RAW_AST  = 1;
const u32 VM_AST      = 1<<1;
const u32 VM_CODE     = 1<<2;
const u32 VM_TIME     = 1<<3;
const u32 VM_EXEC     = 1<<4;
const u32 VM_DETAIL   = 1<<5;
const u32 VM_DEBUG    = 1<<6;
const u32 VM_SYMINFO  = 1<<7;
const u32 VM_PROFILE  = 1<<8;
const u32 VM_PROF_ALL = 1<<9;
const u32 VM_REF_FILE = 1<<10;
const u32 VM_LIMIT    = 1<<11;

std::ostream& help(std::ostream& out) {
    out
    << "\n"
    << "     ,--#-,\n"
    << "<3  / \\____\\  <3\n"
    << "    |_|__A_|\n"
#ifdef _WIN32
    << "use command <chcp 65001> to use unicode.\n"
#endif
    << "\nnasal <option>\n"
    << "option:\n"
    << "   -h,   --help     | get help.\n"
    << "   -v,   --version  | get version.\n"
    << "   -r,   --repl     | use repl interpreter(experimental).\n"
    << "\nnasal [option] <file> [argv]\n"
    << "option:\n"
    << "   -a,   --ast      | view ast after link/optimize process.\n"
    << "         --raw-ast  | view ast without after-processing.\n"
    << "   -c,   --code     | view generated bytecode.\n"
    << "   -s,   --symbol   | show analysed symbol info.\n"
    << "   -e,   --exec     | execute directly.\n"
    << "   -t,   --time     | show execute time.\n"
    << "   -d,   --detail   | get detail info.\n"
    << "   -f,   --ref-file | get referenced files.\n"
    << "   -dbg, --debug    | debug mode.\n"
    << "         --prof     | show profiling result, available in debug mode.\n"
    << "         --prof-all | show profiling result of all files,"
    << "available under debug mode.\n"
    << "         --limit    | use limited execution mode."
    << "file:\n"
    << "   <filename>       | execute file.\n"
    << "argv:\n"
    << "   <args>           | cmd arguments used in program.\n"
    << "\n";
    return out;
}

std::ostream& logo(std::ostream& out) {
    out
    << "\n"
    << "       __                _\n"
    << "    /\\ \\ \\__ _ ___  __ _| |\n"
    << "   /  \\/ / _` / __|/ _` | |\n"
    << "  / /\\  / (_| \\__ \\ (_| | |\n"
    << "  \\_\\ \\/ \\__,_|___/\\__,_|_|\n"
    << "\n"
    << "ver  : " << __nasver__
    << " " << nasal::get_platform() << " " << nasal::get_arch()
    << " (" << __DATE__ << " " << __TIME__ << ")\n"
    << "std  : c++ " << __cplusplus << "\n"
    << "core : " << std::thread::hardware_concurrency() << " core(s)\n"
    << "repo : https://github.com/ValKmjolnir/Nasal-Interpreter\n"
    << "repo : https://gitee.com/valkmjolnir/Nasal-Interpreter\n"
    << "wiki : https://wiki.flightgear.org/Nasal_scripting_language\n"
    << "\n"
    << "presented by fgprc members - http://fgprc.org.cn\n"
    << "\n"
    << "input <nasal -h> to get help .\n\n";
    return out;
}

std::ostream& version(std::ostream& out) {
    std::srand(std::time(nullptr));
    f64 num = 0;
    for(u32 i = 0; i<5; ++i) {
        num = (num+rand())*(1.0/(RAND_MAX+1.0));
    }
    if (num<0.01) {
        nasal::parse::easter_egg();
    }
    out << "nasal interpreter version " << __nasver__;
    out << " " << nasal::get_platform() << " " << nasal::get_arch();
    out << " (" << __DATE__ << " " << __TIME__ << ")\n";
    return out;
}

[[noreturn]]
void err() {
    std::cerr
    << "invalid argument(s).\n"
    << "use <nasal -h> to get help.\n";
    std::exit(1);
}

void execute(
    const std::string& file,
    const std::vector<std::string>& argv,
    const u32 cmd) {

    using clk = std::chrono::high_resolution_clock;
    const auto den = clk::duration::period::den;

    nasal::lexer   lex;
    nasal::parse   parse;
    nasal::linker  ld;
    nasal::codegen gen;

    // lexer scans file to get tokens
    lex.scan(file).chkerr();

    // parser gets lexer's token list to compile
    parse.compile(lex).chkerr();
    if (cmd&VM_RAW_AST) {
        nasal::ast_dumper().dump(parse.tree());
    }

    // linker gets parser's ast and load import files to this ast
    ld.link(parse, cmd&VM_DETAIL).chkerr();
    if (cmd&VM_REF_FILE) {
        if (ld.get_file_list().size()) {
            std::cout << "referenced file(s):\n";
        }
        for(const auto& file: ld.get_file_list()) {
            std::cout << "  " << file << "\n";
        }
    }
    
    // optimizer does simple optimization on ast
    auto opt = std::unique_ptr<nasal::optimizer>(new nasal::optimizer);
    opt->do_optimization(parse.tree());
    if (cmd&VM_AST) {
        nasal::ast_dumper().dump(parse.tree());
    }

    // code generator gets parser's ast and import file list to generate code
    gen.compile(parse, ld, false, cmd&VM_LIMIT).chkerr();
    if (cmd&VM_CODE) {
        gen.print(std::cout);
    }
    if (cmd&VM_SYMINFO) {
        gen.symbol_dump(std::cout);
    }

    // run
    auto start = clk::now();
    if (cmd&VM_DEBUG) {
        auto debugger = std::unique_ptr<nasal::dbg>(new nasal::dbg);
        debugger->run(gen, ld, argv, cmd&VM_PROFILE, cmd&VM_PROF_ALL);
    } else if (cmd&VM_TIME || cmd&VM_EXEC) {
        auto runtime = std::unique_ptr<nasal::vm>(new nasal::vm);
        runtime->set_detail_report_info(cmd&VM_DETAIL);
        runtime->set_limit_mode_flag(cmd&VM_LIMIT);
        runtime->run(gen, ld, argv);
    }

    // get running time
    auto end = clk::now();
    if (cmd&VM_TIME) {
        std::clog << "process exited after ";
        std::clog << (end-start).count()*1.0/den << "s.\n\n";
    }
}

i32 main(i32 argc, const char* argv[]) {
    // output version info
    if (argc<=1) {
        std::clog << logo;
        return 0;
    }

    // run directly or show help
    if (argc==2) {
        std::string s(argv[1]);
        if (s=="-h" || s=="--help") {
            std::clog << help;
        } else if (s=="-v" || s=="--version") {
            std::clog << version;
        } else if (s=="-r" || s=="--repl") {
            auto repl = std::unique_ptr<nasal::repl::repl>(new nasal::repl::repl);
            repl->execute();
        } else if (s[0]!='-') {
            execute(s, {}, VM_EXEC);
        } else {
            err();
        }
        return 0;
    }

    // execute with arguments
    const std::unordered_map<std::string, u32> cmdlst = {
        {"--raw-ast", VM_RAW_AST},
        {"--ast", VM_AST},
        {"-a", VM_AST},
        {"--code", VM_CODE},
        {"-c", VM_CODE},
        {"--symbol", VM_SYMINFO},
        {"-s", VM_SYMINFO},
        {"--exec", VM_EXEC},
        {"-e", VM_EXEC},
        {"--time", VM_TIME|VM_EXEC},
        {"-t", VM_TIME|VM_EXEC},
        {"--detail", VM_DETAIL|VM_EXEC},
        {"-d", VM_DETAIL|VM_EXEC},
        {"--debug", VM_DEBUG},
        {"-dbg", VM_DEBUG},
        {"--prof", VM_PROFILE},
        {"--prof-all", VM_PROF_ALL},
        {"-f", VM_REF_FILE},
        {"--ref-file", VM_REF_FILE},
        {"--limit", VM_LIMIT|VM_EXEC}
    };
    u32 cmd = 0;
    std::string filename = "";
    std::vector<std::string> vm_argv;
    for(i32 i = 1; i<argc; ++i) {
        if (cmdlst.count(argv[i])) {
            cmd |= cmdlst.at(argv[i]);
        } else if (!filename.length()) {
            filename = argv[i];
        } else {
            vm_argv.push_back(argv[i]);
        }
    }
    if (!filename.length()) {
        err();
    }
    execute(filename, vm_argv, cmd? cmd:VM_EXEC);
    return 0;
}